package adventure.misc;

import adventure.placeable.Fightable;
import adventure.placeable.Mobile;
import colour.ColourUtil;
import java.util.Random;
import player.Player;
import server.util.Broadcaster;

/**
 *
 * @author Michael Hanns
 *
 */
public class CombatImp implements Combat {

    private Broadcaster b;
    private Random random;
    private String criticalHit = ColourUtil.colouriseAlternate("Critical hit! ",
            ColourUtil.criticalHit);

    public CombatImp(Broadcaster br) {
        b = br;
        random = new Random();
    }

    @Override
    public synchronized void fight(Player attacker, Player defender) {
        if (sameLocation((Fightable) attacker, (Fightable) defender)
                && stillAlive((Fightable) attacker, (Fightable) defender)) {

            double critMod = attackerCrit((Fightable) attacker, (Fightable) defender);
            boolean evadeSuccess = false;

            if (critMod == 1) {
                evadeSuccess = defenderEvade((Fightable) attacker, (Fightable) defender);
            }

            if (!evadeSuccess) {
                int dmgRcvd = dealDamage((Fightable) attacker, critMod, (Fightable) defender);

                String attackoutput = "You attack " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + " for " + ColourUtil.colourise(dmgRcvd + "", ColourUtil.damage) + " damage!";
                String defendoutput = ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + " attacks you for " + ColourUtil.colourise(dmgRcvd + "", ColourUtil.damage) + " damage!";
                String observeroutput = ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + " attacks " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + " for " + ColourUtil.colourise(dmgRcvd + "", ColourUtil.damage) + " damage!";

                if (critMod > 1) {
                    attackoutput = criticalHit + attackoutput;
                    defendoutput = criticalHit + defendoutput;
                    observeroutput = criticalHit + observeroutput;
                }

                if (defender.isDead()) {
                    attackoutput += "\nYou have slain " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + "!";
                    defendoutput += "\n" + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + " has slain you!";
                    observeroutput += "\n" + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + " has slain " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + "!";

                    // Players gain experience from combat!
                    // Get level difference
                    int xpGained = getXPReward((Fightable) attacker, (Fightable) defender); // Gain standard
                    attackoutput += "\n" + ((Player) attacker).gainXP(xpGained, false);

                    // Lucky players get bonus experience!
                    xpGained = getBonusXPReward((Fightable) attacker, xpGained, (Fightable) defender);
                    if (xpGained > 0) {
                        attackoutput += "\n" + ((Player) attacker).gainXP(xpGained, true);
                    }

                    attacker.stopFighting((Fightable) defender);
                    defender.stopFighting((Fightable) attacker);
                }

                b.broadcastToPlayer(attacker, attackoutput);
                b.broadcastToPlayer(defender, defendoutput);
                b.broadcastLocalToPlayers(new Player[]{attacker, defender},
                        observeroutput);

            } else {
                String attackoutput = ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + " has evaded your attack!";
                String defendoutput = "You have evaded " + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + "'s attack!";
                String observeroutput = ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + " has evaded " + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + "'s attack!";

                b.broadcastToPlayer(attacker, attackoutput);
                b.broadcastToPlayer(defender, defendoutput);
                b.broadcastLocalToPlayers(new Player[]{attacker, defender},
                        observeroutput);
            }
        } else {
            attacker.stopFighting((Fightable) defender);
            defender.stopFighting((Fightable) attacker);
        }
    }

    @Override
    public synchronized void fight(Player attacker, Mobile defender) {
        if (sameLocation((Fightable) attacker, (Fightable) defender)
                && stillAlive((Fightable) attacker, (Fightable) defender)) {

            double critMod = attackerCrit((Fightable) attacker, (Fightable) defender);
            boolean evadeSuccess = false;

            if (critMod == 1) {
                evadeSuccess = defenderEvade((Fightable) attacker, (Fightable) defender);
            }

            if (!evadeSuccess) {
                int dmgRcvd = dealDamage((Fightable) attacker, critMod, (Fightable) defender);

                String attackoutput = "You attack " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile) + " for " + ColourUtil.colourise(dmgRcvd + "", ColourUtil.damage) + " damage!";
                String observeroutput = ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + " attacks " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile) + " for " + ColourUtil.colourise(dmgRcvd + "", ColourUtil.damage) + " damage!";

                if (critMod > 1) {
                    attackoutput = criticalHit + attackoutput;
                    observeroutput = criticalHit + observeroutput;
                }

                if (defender.isDead()) {
                    attackoutput = attackoutput + "\nYou have slain " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile) + "!";
                    observeroutput += "\n" + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + " has slain " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile) + "!";

                    // Players gain experience from combat!
                    // Get level difference
                    int xpGained = getXPReward((Fightable) attacker, (Fightable) defender); // Gain standard
                    attackoutput = attackoutput + "\n" + ((Player) attacker).gainXP(xpGained, false);

                    // Lucky players get bonus experience!
                    xpGained = getBonusXPReward((Fightable) attacker, xpGained, (Fightable) defender);
                    if (xpGained > 0) {
                        attackoutput = attackoutput + "\n" + ((Player) attacker).gainXP(xpGained, true);
                    }

                    attacker.stopFighting((Fightable) defender);
                    defender.stopFighting(attacker);
                }

                b.broadcastToPlayer(attacker, attackoutput);
                b.broadcastLocalToPlayer(attacker, observeroutput);

            } else {
                String attackoutput = ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile) + " has evaded your attack!";
                String observeroutput = ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile) + " has evaded " + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + "'s attack!";

                b.broadcastToPlayer(attacker, attackoutput);
                b.broadcastLocalToPlayer(attacker, observeroutput);

            }
        } else {
            attacker.stopFighting((Fightable) defender);
            defender.stopFighting(attacker);
        }

    }

    @Override
    public synchronized void fight(Mobile attacker, Player defender) {
        if (sameLocation((Fightable) attacker, (Fightable) defender)
                && stillAlive((Fightable) attacker, (Fightable) defender)) {

            String attackoutput = "";
            String observeroutput = "";
            boolean surrender = false;

            if (attacker.isNearlyDead()) {
                if (new Random().nextInt(100) < attacker.getSpeed() + attacker.getLuck()) {
                    // If they're quick, they might do an action
                    if (attacker.hasHealingPotion()) {
                        // If they can heal, they heal
                        attackoutput = attacker.drinkHealingPotion();
                    } else {
                        // Mercy
                        defender.stopFighting((Fightable) attacker);
                        attacker.stopFighting(defender);

                        attackoutput = ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + " falls back and begs for mercy.\n";
                        observeroutput = ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + " falls back and begs for mercy.\n";
                        surrender = true;
                    }
                } else {
                    // 'til the death
                    attackoutput = ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + " recomposes itself and lunges at you!\n";
                    observeroutput = ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + " recomposes itself and lunges at "
                            + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + "!\n";
                }
            }

            if (!surrender) {
                double critMod = attackerCrit((Fightable) attacker, (Fightable) defender);
                boolean evadeSuccess = false;

                if (critMod == 1) {
                    evadeSuccess = defenderEvade((Fightable) attacker, (Fightable) defender);
                }

                if (!evadeSuccess) {
                    int dmgRcvd = dealDamage((Fightable) attacker, critMod, (Fightable) defender);

                    attackoutput += ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + " attacks you for " + ColourUtil.colourise(dmgRcvd + "", ColourUtil.damage) + " damage!";
                    observeroutput += ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + " attacks "
                            + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + " for " + ColourUtil.colourise(dmgRcvd + "", ColourUtil.damage) + " damage!";

                    if (critMod > 1) {
                        attackoutput = criticalHit + attackoutput;
                        observeroutput = criticalHit + observeroutput;
                    }

                    if (defender.isDead()) {
                        attackoutput += "\nYou been have slain by " + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + "!";
                        observeroutput += "\n" + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player)
                                + " has been have slain by "
                                + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + "!";

                        attacker.stopFighting(defender);
                        defender.stopFighting((Fightable) attacker);
                    }

                    b.broadcastToPlayer(defender, attackoutput);
                    b.broadcastLocalToPlayer(defender, observeroutput);

                } else {
                    attackoutput = "You evade " + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.mobile) + "'s attack!";
                    observeroutput = ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + " evades "
                            + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + "'s attack!";

                    b.broadcastToPlayer(defender, attackoutput);
                    b.broadcastLocalToPlayer(defender, observeroutput);
                }
            } else {
                attacker.stopFighting(defender);
                defender.stopFighting((Fightable) attacker);
            }
        }
    }

    private double attackerCrit(Fightable attacker, Fightable defender) {
        double roll = random.nextDouble();

        // Determine if attack is critical
        boolean critical = roll <= (double) ((double) attacker.getAttack() * (1 + ((double) attacker.getLuck() / 100))) / 200;
        double critMod = 1;

        if (critical) {
            critMod = Math.max(1, 2 - ((double) defender.getHelmMod() / 100));
        }

        return critMod;
    }

    private boolean defenderEvade(Fightable attacker, Fightable defender) {
        // If not a critical, risk opponent evading.
        double roll = random.nextDouble();
        int speedDiff = defender.getSpeed() - attacker.getSpeed();
        boolean evadeSuccess = roll <= (double) ((defender.getSpeed() + speedDiff - (double) defender.getBootsMod() / 4)
                * (1 + ((double) defender.getLuck() / 100))) / 200;
        return evadeSuccess;
    }

    private int dealDamage(Fightable attacker, double critMod, Fightable defender) {
        int dmgDiff = attacker.getAttack() - defender.getDefence();
        int dmgRcvd =
                Math.max((int) (critMod * (attacker.getAttack() + dmgDiff + attacker.getWeaponMod()))
                - defender.getChestMod() - defender.getBootsMod(), 0);

        if (!defender.isImmortal()) {
            defender.modifyHealth(-dmgRcvd);
        }

        return dmgRcvd;
    }

    private int getXPReward(Fightable attacker, Fightable defender) {
        int levelDiff = 10 + defender.getLevel() - attacker.getLevel();
        levelDiff = Math.max(levelDiff, 1);

        // Get attack/defence difference
        int statDiff = defender.getAttack() + defender.getDefence()
                - attacker.getAttack() - attacker.getDefence();
        statDiff = Math.max(statDiff, 1);

        int xpGained = levelDiff * statDiff;

        return xpGained;
    }

    private int getBonusXPReward(Fightable attacker, int xpGained, Fightable defender) {
        double bonusRoll = random.nextDouble();
        boolean bonusXP = bonusRoll <= ((double) attacker.getLuck() / 100);
        if (bonusXP) {
            xpGained = Math.max(1, (int) (xpGained * bonusRoll));
            return xpGained;
        }
        return 0;
    }

    private boolean sameLocation(Fightable attacker, Fightable defender) {
        if (attacker.x() == defender.x() && attacker.y() == defender.y()
                && attacker.z() == defender.z()) {
            return true;
        }
        return false;
    }

    private boolean stillAlive(Fightable attacker, Fightable defender) {
        if (attacker.isDead() || defender.isDead()) {
            return false;
        }
        return true;
    }
}
